package com.lonewalker.snail.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lonewalker.snail.common.base.ApiResult;
import com.lonewalker.snail.constant.CommonConstant;
import com.lonewalker.snail.constant.OperationTypeEnum;
import com.lonewalker.snail.constant.flow.ProcessConstant;
import com.lonewalker.snail.constant.flow.ProcessExceptionEnum;
import com.lonewalker.snail.domain.entity.ProcessInstance;
import com.lonewalker.snail.domain.entity.SysUser;
import com.lonewalker.snail.domain.request.ModifyInstanceRequest;
import com.lonewalker.snail.domain.request.RejectInstanceRequest;
import com.lonewalker.snail.domain.request.SetVariableRequest;
import com.lonewalker.snail.domain.request.StartProcessRequest;
import com.lonewalker.snail.mapper.HisActivityMapper;
import com.lonewalker.snail.mapper.ProcessInstanceMapper;
import com.lonewalker.snail.service.ProcessInstanceService;
import com.lonewalker.snail.service.ProcessOperateLogService;
import com.lonewalker.snail.util.AssertUtil;
import lombok.RequiredArgsConstructor;
import org.camunda.bpm.engine.*;
import org.camunda.bpm.engine.history.HistoricActivityInstance;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.runtime.ActivityInstance;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.FlowNode;
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.bpmn.instance.UserTask;
import org.camunda.bpm.model.xml.instance.ModelElementInstance;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.*;
import java.util.stream.Collectors;


/**
 * 流程实例相关方法
 *
 * @author lonewalker
 */
@RequiredArgsConstructor
@Service("processInstanceService")
public class ProcessInstanceServiceImpl extends ServiceImpl<ProcessInstanceMapper, ProcessInstance> implements ProcessInstanceService {

    private final RuntimeService runtimeService;
    private final HistoryService historyService;
    private final TaskService taskService;
    private final RepositoryService repositoryService;
    private final HisActivityMapper hisActivityMapper;
    private final ProcessOperateLogService processOperateLogService;


    @Override
    public ApiResult<String> startProcessInstanceByKey(SysUser user, StartProcessRequest request) {
        String processDefinitionKey = request.getProcessDefinitionKey();
        Map<String, Object> paramMap = new HashMap<>(10);
        List<String> assigneeList = new ArrayList<>();
        assigneeList.add("10089,10088");


        paramMap.put("initiator", "10086");
        paramMap.put("assigneeList", assigneeList);
//        paramMap.put("manager", "10087");
//        paramMap.put("head", "10088");
//        paramMap.put("day", 4);
//        List<String> managerList = new ArrayList<>();
//        managerList.add("10087");
//        managerList.add("10088");
//        paramMap.put("candidateUsers", managerList);
//        List<String> headList = new ArrayList<>();
//        headList.add("10089");
//        headList.add("10090");
//        List<String> generals = new ArrayList<>();
//        generals.add("10091");
//        generals.add("10092");
//        generals.add("10093");
//        paramMap.put("managerList", managerList);
//        paramMap.put("headList", headList);
//        paramMap.put("generals", generals);

        //businessKey用于查询用户对应业务的待办任务
        org.camunda.bpm.engine.runtime.ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, request.getBusinessKey(), paramMap);
        AssertUtil.checkService(ObjectUtil.isNull(processInstance), ProcessExceptionEnum.START_PROCESS_INSTANCE_FAIL.getDesc());
        return ApiResult.success(processInstance.getProcessInstanceId());
    }

    @Override
    public ApiResult<String> startProcessInstanceById(SysUser user, StartProcessRequest request) {
        String processDefinitionId = request.getProcessDefinitionId();
        Map<String, Object> paramMap = new HashMap<>(10);
        paramMap.put("initiator", user.getId().toString());
        paramMap.put("manager", "10087");
        paramMap.put("head", "10088");

        org.camunda.bpm.engine.runtime.ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, request.getBusinessKey(), paramMap);
        AssertUtil.checkService(ObjectUtil.isNull(processInstance), ProcessExceptionEnum.START_PROCESS_INSTANCE_FAIL.getDesc());
        return ApiResult.success(processInstance.getProcessInstanceId());
    }

    @Override
    public ApiResult<String> rejectProcessInstance(RejectInstanceRequest request) {
        String processInstanceId = request.getProcessInstanceId();
        ActivityInstance activity = runtimeService.getActivityInstance(processInstanceId);
        runtimeService.createProcessInstanceModification(processInstanceId)
                .cancelActivityInstance(activity.getId())
                .setAnnotation("驳回")
                .startBeforeActivity(request.getTargetNodeId())
                .execute();
        return ApiResult.successMessage("驳回成功");
    }

    @Override
    public ApiResult<String> setVariableByInstance(SetVariableRequest request) {

        Map<String, Object> variables = new HashMap<>(4);
        variables.put("weapon", "丈八蛇矛");
        runtimeService.setVariables(request.getProcessInstanceId(), variables);
        return ApiResult.successMessage("设置变量成功");
    }

    @Override
    public ApiResult<String> suspendProcessInstanceById(String processInstanceId) {
        runtimeService.suspendProcessInstanceById(processInstanceId);
        return ApiResult.successMessage("挂起成功");
    }

    @Override
    public ApiResult<String> activateProcessInstanceById(String processInstanceId) {
        runtimeService.activateProcessInstanceById(processInstanceId);
        return ApiResult.successMessage("激活成功");
    }

    @Override
    public ApiResult<String> deleteProcessInstance(String processInstanceId) {
        runtimeService.deleteProcessInstance(processInstanceId, "不需要了");
        return ApiResult.successMessage("删除成功");
    }

    @Override
    public ApiResult<String> modifyProcessInstance(ModifyInstanceRequest requestParam) {
        String processInstanceId = requestParam.getProcessInstanceId();
        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstanceId);
        //这里还可以设置参数，需要可使用
        runtimeService.createProcessInstanceModification(processInstanceId)
                .cancelActivityInstance(activityInstance.getId())
                .startBeforeActivity(requestParam.getTargetNodeId())
                //.setVariable()
                .execute();
        return ApiResult.successMessage("操作成功");
    }

    /**
     * 通过监听器获取节点审批人，则这里就不能使用startBeforeActivity了。
     * 原因：比如A节点设定的审批人是总监，但是总监是个角色，原先总监对应的人是张三，某条流程实例驳回到A节点的时候张三离职了，李四是总监了，那就需要再触发一次监听器
     *
     * @param requestParam 请求参数
     * @return 提示信息
     */
    @Override
    public ApiResult<String> modifyProcessInstanceV2(ModifyInstanceRequest requestParam) {
        String processInstanceId = requestParam.getProcessInstanceId();
        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstanceId);
        //本项目所有的bpmn中的发起人节点id均为root
        if (OperationTypeEnum.APPROVAL_REJECTION.getCode().equals(requestParam.getOperationType()) && ProcessConstant.ROOT.equals(requestParam.getTargetNodeId())) {
            //驳回并且目标节点是发起人节点
            runtimeService.createProcessInstanceModification(processInstanceId)
                    .cancelActivityInstance(activityInstance.getId())
                    .startBeforeActivity(requestParam.getTargetNodeId())
                    .execute();
        }

        //获取实例的流程定义 这里主要是为了拿到节点前的那条线的Id
        //TODO 这里后续看看能否优化一下
        List<HistoricActivityInstance> hisActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).finished().list();
        List<String> activityIds = hisActivityList.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
        org.camunda.bpm.engine.runtime.ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        BpmnModelInstance bpmnModel = repositoryService.getBpmnModelInstance(processInstance.getProcessDefinitionId());
        ModelElementInstance modelElement = bpmnModel.getModelElementById(requestParam.getTargetNodeId());
        UserTask userTask = (UserTask) modelElement;
        Collection<SequenceFlow> incoming = userTask.getIncoming();
        String transitionId = "";
        for (SequenceFlow sequenceFlow : incoming) {
            FlowNode source = sequenceFlow.getSource();
            if (activityIds.contains(source.getId())) {
                transitionId = sequenceFlow.getId();
                break;
            }
        }

        //获取当前环节实例
        ActivityInstance activity = runtimeService.getActivityInstance(processInstanceId);
        runtimeService.createProcessInstanceModification(processInstanceId)
                .cancelActivityInstance(activity.getId())
                .startTransition(transitionId)
                .execute();
        return ApiResult.successMessage("操作成功");
    }

    /**
     * 流程实例如果结束,流程历史所有节点状态均为1
     * 流程如果没结束，当前节点状态为0，各节点最小序列计数器与当前节点的最小计数器比较，小于的都是完成节点
     *
     * @param processInstanceId 流程实例id
     * @return 节点以及状态值
     */
    @Override
    public ApiResult<Map<String, Integer>> queryInstanceNodeState(String processInstanceId) {
        org.camunda.bpm.engine.runtime.ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (ObjectUtil.isNull(processInstance)) {
            List<HistoricActivityInstance> allHisActivityList = new ArrayList<>();
            List<HistoricActivityInstance> userTaskList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType(ActivityTypes.TASK_USER_TASK).list();
            List<HistoricActivityInstance> sendTaskList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType(ActivityTypes.TASK_SEND_TASK).list();
            allHisActivityList.addAll(userTaskList);
            allHisActivityList.addAll(sendTaskList);
            if (CollUtil.isNotEmpty(allHisActivityList)) {
                Map<String, Integer> resultMap = allHisActivityList.stream().collect(Collectors.toMap(HistoricActivityInstance::getActivityId, d -> 1));
                return ApiResult.success(resultMap);
            }
        } else {
            String currentNodeId;
            Map<String, Integer> nodeMap = new HashMap<>(10);
            List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
            if (CollUtil.isNotEmpty(tasks)) {
                currentNodeId = tasks.get(0).getTaskDefinitionKey();
                nodeMap.put(currentNodeId, CommonConstant.ZERO);
                if (ProcessConstant.ROOT.equals(currentNodeId)) {
                    //所有bpmn中的发起人节点id都是root
                    return ApiResult.success(nodeMap);
                }
                //查询当前节点对应的最小计数器  多实例节点在节点id后面拼接 #multiInstanceBody
                Integer sequenceCounter = hisActivityMapper.getBodySequenceCounter(processInstanceId, currentNodeId + ProcessConstant.MULTI_INSTANCE_BODY);
                //节点分组每组最小计数器小于当前节点最小计数器的为完成节点
                Assert.isTrue(ObjectUtil.isNotNull(sequenceCounter), "未查询到对应的序列计数器");
                List<String> completionNodes = hisActivityMapper.getCompletionNodes(processInstanceId, sequenceCounter);
                if (CollUtil.isNotEmpty(completionNodes)) {
                    completionNodes.forEach(d -> nodeMap.put(d, CommonConstant.ONE));
                }
            }
            return ApiResult.success(nodeMap);
        }
        return null;
    }
}
